package multiplexing

import (
	"sort"
	"sync"
	"sync/atomic"
)

const (
	steps                   = 20
	minBitSize              = 6
	calibrateCallsThreshold = 42000
	maxPercentile           = 0.95
)

var (
	defaultPool BytePool
)

func Get() *ByteBuffer  { return defaultPool.Get() }
func Put(b *ByteBuffer) { defaultPool.Put(b) }

type BytePool struct {
	// 是否正在校准中
	calibrating uint64
	// 默认大小
	defaultSize uint64
	// 最大大小
	maxSize uint64
	// 不同buf大小的调用次数
	calls [steps]uint64
	// 内部的对象复用池
	innerPool sync.Pool
}

// 从对象复用池里获取一个对象
func (p *BytePool) Get() *ByteBuffer {
	v := p.innerPool.Get()
	if v != nil {
		return v.(*ByteBuffer)
	}
	return &ByteBuffer{
		B: make([]byte, 0, atomic.LoadUint64(&p.defaultSize)),
	}
}

// Put 放入bytebuffer对象
func (p *BytePool) Put(b *ByteBuffer) {
	// 根据bytebuffer的大小来获取对应的索引
	idx := index(len(b.B))

	// 每一次放入对象池中后, 会根据buf的大小,来让calls自增1, 如果超过调用的阈值, 需要重新校准
	if atomic.AddUint64(&p.calls[idx], 1) > calibrateCallsThreshold {
		p.calibrate()
	}

	// 获取对象池中最大的buf的大小
	maxSize := int(atomic.LoadUint64(&p.maxSize))

	// 如果最大的大小为0或者将要放入的buf的容量小于等于最大大小
	// 重置buf为0,放入到对象池中
	// 反之, 则不作任何处理
	if maxSize == 0 || cap(b.B) <= maxSize {
		b.Reset()
		p.innerPool.Put(b)
	}

}

// 校准
func (p *BytePool) calibrate() {
	// 并发控制
	if !atomic.CompareAndSwapUint64(&p.calibrating, 0, 1) {
		return
	}

	a := make(callSizes, 0, steps)
	var callsSum uint64

	for i := uint64(0); i < steps; i++ {
		calls := atomic.SwapUint64(&p.calls[i], 0)
		callsSum += calls
		a = append(a, callSize{
			calls: calls,
			size:  minBitSize << i,
		})
	}
	// 从大到校排序
	sort.Sort(a)

	defaultSize := a[0].size
	maxSize := defaultSize

	maxSum := uint64(float64(callsSum) * maxPercentile)
	callsSum = 0

	for i := 0; i < steps; i++ {
		if callsSum > maxSum {
			break
		}
		callsSum += a[i].calls
		size := a[i].size

		if size > maxSize {
			maxSize = size
		}
	}

	atomic.StoreUint64(&p.defaultSize, defaultSize)
	atomic.StoreUint64(&p.maxSize, maxSize)
	atomic.StoreUint64(&p.calibrating, 0)
}

type callSize struct {
	calls uint64
	size  uint64
}

type callSizes []callSize

func (ci callSizes) Len() int {
	return len(ci)
}

func (ci callSizes) Less(i, j int) bool {
	return ci[i].calls > ci[j].calls
}

func (ci callSizes) Swap(i, j int) {
	ci[i], ci[j] = ci[j], ci[i]
}

// 限制[]byte的大小的索引
func index(n int) int {
	n--
	n >>= minBitSize
	idx := 0
	for n > 0 {
		n >>= 1
		idx++
	}
	if idx >= steps {
		idx = steps - 1
	}
	return idx
}
